/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.tactics;

import java.util.LinkedList;

import robocup.component.Prophet;
import robocup.component.StateTactic;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.actionfactories.AttentionToActionFactory;
import robocup.component.actionfactories.SayActionFactory;
import robocup.component.actionfactories.TurnNeckActionFactory;
import robocup.component.actionfactories.ViewModeActionFactory;
import robocup.component.actions.Action;
import robocup.component.actions.AttentionToAction;
import robocup.component.actions.ChangeViewModeAction;
import robocup.component.actions.PointToAction;
import robocup.component.actions.SayAction;
import robocup.component.actions.TurnNeckAction;
import robocup.component.situations.InterceptBallSituation;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;

/**
 * Abstract State Class defining the methods used in all child classes.
 * 
 * In this framework a state describes the agents intention. Whether a given 
 * state suits a given world depends on the states preconditions 
 * ({@link #preCondition()}). One of the main facts to be checked by this method
 * is whether the state is allowed in the given play mode with all allowed 
 * stateXplaymode combinations given in the player's *.conf file. All fitting
 * intentions are then weighted by their benefit ({@link #successBenefit()}) and
 * their success probability ({@link #successProbability()}) and the state with
 * the highest score is choosen ({@link #assessment}). Once a state is chosen, 
 * the corresponding actions are calculated ({@link #getMainAction()} etc.).
 * 
 * @see StateTactic
 * @see StateEvaluation
 */
public abstract class AbstractState {

    protected final String empty = "";

    /**
     * State IDs and some boolean methods.
     */
    public enum STATES {

        BEFORE_KICKOFF,
        DRIBBLE,
        GOALIE_KICK_IN,
        PLAYER_KICK_IN,
        PLAYER_WAIT,
        GOALKEEPER,
        INTERCEPT_BALL,
        PASS,
        SEARCH_BALL,
        SHOOT_GOAL,
        START_POSITION,
        DUMMY_STATE, INTERCEPT_OPPONENT;

        /**
         *
         * @return true for all states where the ball is kicked
         */
        public boolean isKickState() {
            switch (this) {
                case PASS:
                case SHOOT_GOAL:
                    return true;
                default:
                    return false;
            }
        }

        /**
         *
         * @return true for all states where the player has the ball in the next
         *         cycle
         */
        public boolean isBallControlState() {
            switch (this) {
                case DRIBBLE:
                case GOALIE_KICK_IN:
                    return true;
                default:
                    return false;
            }

        }
    }
    /** The current state id * */
    protected STATES state = STATES.DUMMY_STATE;
    /** The StateEvaluation (which holds this state) * */
    protected StateEvaluation parent;
    /**
     * Main action.
     *
     * This actions has to set by the state, using the corresponding method.
     */
    protected Action mainAction = null;
    /**
     * Change View Mode action.
     *
     * This actions has to set by the state, using the corresponding method.
     */
    protected ChangeViewModeAction viewModeAction = null;
    /**
     * Turn Neck action.
     *
     * This actions has to set by the state, using the corresponding method.
     */
    protected TurnNeckAction turnNeckAction = null;
    /**
     * Point To action.
     *
     * This actions has to set by the state, using the corresponding method.
     */
    protected PointToAction pointAtAction = null;
    /**
     * Say action.
     *
     * This actions has to set by the state, using the corresponding method.
     */
    protected SayAction sayAction = null;
    /**
     * Attention to action.
     *
     * This actions has to set by the state, using the corresponding method.
     */
    protected AttentionToAction attentionToAction = null;
    /**
     * The states assigned assesment. The state with the highest assessment will
     * be chosen among all possible states [0.0..1.0].
     */
    protected double assessment = -1.0;
    /**
     * this string will be shown in the gui together with the state type.
     */
    protected String subStateString = "";

    /**
     * Constructor of most states. A state is an intention
     * of an agent. A behaviour has a precondition, an asessment, and creates
     * certain kinds of actions as output.
     *
     * Subclasses may call super(parent,playModes);
     *
     * @param parent -
     *            the StateEvaluation - holds and evaluates all states and
     *            refers to important objects (such as the worldmodel)
     */
    public AbstractState(StateEvaluation parent) {

        this.parent = parent;
    }

    /**
     * This method resets the state, i.e. all previous calculated actions,
     * values and probabilities.
     */
    public void reset() {
        assessment = -1.0;
        this.resetActions();
    }

    /**
     * sets all actions to "null".
     *
     */
    public void resetActions() {
        mainAction = null;
        viewModeAction = null;
        turnNeckAction = null;
        pointAtAction = null;
        sayAction = null;
        attentionToAction = null;
    }

    /**
     * sets the substate string. empty for the abstract state.
     *
     */
    public void setSubStateString() {
        subStateString = empty;
    }

    /**
     * This method gathers all preConditions and checks them in order to filter
     * all impossible States
     *
     * @return true, if this State is currently available!
     */
    public abstract boolean preCondition();

    /**
     * This method should calculate a value, that will be used to decide, how
     * "good" the application of this state is. Values may range from 0 to 1,
     * the higher the value, the more should this state be prefered. In most
     * cases, states are assessed by their success-probability and the value of
     * the resulting state of the world.
     *
     * @return a value between 0 and 1, depending on the profit and risks of the
     *         state!
     */
    public void calculateStateAssessment() {
        this.assessment = successProbability() * successBenefit();
    }

    /**
     * simple getter.
     *
     * @return the state'S assessment
     */
    public double getStateAssessment() {
        if (this.assessment < 0) {
            calculateStateAssessment();
        }
        return this.assessment;
    }

    /**
     * This method is used for calculating the assessment of
     * the given state, depending on the current world model.
     * The Success-Probability is a value from 0..1, expressing an estimate on the
     * chance of succeeding with the action defined with this state.
     *
     * @param wm -
     *            the current worldmodel
     * @return - an estimate between 0 and 1
     */
    protected abstract double successProbability();

    /**
     * This method is used for calculating the assessment of
     * the given state, depending on the current world model.
     * The Success-Benefit expressess the benefit resulting from the chosen state
     * (zero is very bad, 1 is very good).
     *
     * @param wm -
     *            the current worldmodel
     * @return - an estimate between 0 and 1
     */
    protected abstract double successBenefit();

    /**
     * the main action has to be calculated seperatly by each state
     */
    public abstract void calculateMainAction();

    /**
     * This is the default viewmode implementation based on our synchro style
     */
    public void calculateViewModeAction() {
        viewModeAction = ViewModeActionFactory.getAction(this.parent.getParent().getAgent().getSynchro());
    }

    /**
     * This is the default attention to action, which should be overridden in
     * special states
     */
    public void calculateAttentionToAction() {
        attentionToAction = AttentionToActionFactory.getAction(getWorld());

        // filter if not necessary
        if (attentionToAction.getCommunicator().getNumber() == this.getWorld().getSelfRef().getLastHearedPlayer() && this.getWorld().getSelfRef().getLastHearBodyCycle() == this.getWorld().getBodyCycle()) {
            this.attentionToAction = null;
        }
    }

    /**
     * This is the default turn neck action, which is based upon the
     * NeckRotator
     */
    public void calculateTurnNeckAction() {
        turnNeckAction = TurnNeckActionFactory.getAction(getWorld(), state,
                mainAction, viewModeAction);
    }

    /**
     * This is the default pointTo Action implementation
     */
    public void calculatePointToAction() {
    }

    /**
     * This is the default say-action: overwrite in substates if needed!
     */
    public void calculateSayAction() {
        sayAction = SayActionFactory.getAction(getWorld());

    }

    /**
     * This Method returns true if the current play mode is inside the array of
     * allowed play modes.
     *
     * @return true, if the current playMode is contained in the playMode array;
     *         false otherwise
     */
    protected boolean checkPlayMode() {

        LinkedList<PLAY_MODE> modes = this.getPlayer().getPlayModes(this.state);
        if (modes == null || modes.size() == 0) {
            return false;
        }

        for (PLAY_MODE m : modes) {
            if (m.contains(this.getWorld().getPlayMode())) {
                return true;
            }
        }

        return false;
    }

    /**
     * This Method checks, if the value for hasBall is equal to the situation
     * recognized in the worldmodel
     *
     * @return true, if hasBall is equal to the situation
     */
    protected boolean checkPlayerHasBall() {
        return this.parent.getPlayer().canKick(this.parent.getBall());
    }

    /**
     * @return true if the player can kick and an opponent can kick at the same
     *         time or of all teammates that can kick he has the smallest number
     *         (prevents that effect of two teammates kciking the same ball).
     *         This way we avoid to of our players kicking in the same cycle.
     */
    protected boolean checkMyBall() {
        //SIMPLIFIED FOR SOURCE CODE RELEASE
        if (true) {
            return this.checkPlayerHasBall();
        }

        if (this.checkPlayerHasBall() && this.getProphet().getPlayersThatCanKick().size() < 2 && !this.getPlayer().kickedStandard()) {
            return true;
        }

        if (!this.checkPlayerHasBall() || this.getProphet().getPlayersThatCanKick().size() == 0 || this.getPlayer().kickedStandard()) {

            return false;
        }

        LinkedList<Player> playersWithBall = this.getProphet().getPlayersThatCanKick();

        int smallestNumber = 12;

        for (Player p : playersWithBall) {
            if (!p.isFriend()) {
                return true;
            } else {
                if (p.getNumber() < smallestNumber) {
                    smallestNumber = p.getNumber();
                }
            }
        }

        return this.getPlayer().getNumber() == smallestNumber;
    }

    /**
     * Player is fastest to ball.
     *
     * @return true, if the player is fastest to the ball
     *
     * @see InterceptBallSituation
     */
    protected boolean checkIAmFastestToBall() {

        return this.getProphet().isFastestPlayerToBall(this.getPlayer());
    }

    /**
     * Returns the global world model.
     *
     * @return the world model of StateEvaluation
     */
    public WorldModel getWorld() {

        return this.parent.getWorld();
    }

    /**
     * Returns the global prophet.
     *
     * @return teh world model of StateEvaluation
     */
    public Prophet getProphet() {

        return this.parent.getProphet();
    }

    /**
     * The type is a String for debugging, containing the name of the class
     * given by <b>this</b>-object
     *
     * @return the name of the class of the current state
     */
    @Override
    public String toString() {
        if (state != null) {
            return state.toString();
        } else {
            return "UNKNOWN";
        }
    }

    /**
     * Returns the current state.
     *
     * @return the current state.
     */
    public STATES getState() {

        return this.state;
    }

    /**
     * Returns the current sub state string.
     *
     * @return the current sub state string.
     */
    public String getSubStateString() {

        return this.subStateString;
    }

    /**
     * @return the calculated attentionToAction
     */
    public AttentionToAction getAttentionToAction() {
        return attentionToAction;
    }

    /**
     * @return the calculated mainAction
     */
    public Action getMainAction() {
        return mainAction;
    }

    /**
     * @return the calculated pointToAction
     */
    public PointToAction getPointAtAction() {
        return pointAtAction;
    }

    /**
     * @return the calculated sayAction
     */
    public SayAction getSayAction() {
        return sayAction;
    }

    /**
     * @return the calculated turnNeckAction
     */
    public TurnNeckAction getTurnNeckAction() {
        return turnNeckAction;
    }

    /**
     * @return the viewModeAction
     */
    public ChangeViewModeAction getViewModeAction() {
        return viewModeAction;
    }

    /**
     * @return the executing player - which usually is the self
     */
    public Player getPlayer() {
        return parent.getPlayer();
    }

    /**
     * @return the ball
     */
    public Ball getBall() {
        return parent.getBall();
    }
}
